#!/bin/bash
# This script is released under the GNU General Public License 3.0
# Check the COPYING file included with this distribution

# partition_finalise
# Creates filesystem, also with encryption, mounts the partitions
# parameters (required)
#  CONFIG_LIST: One string items of defined FSspec
#
# returns 0 on success
# returns $ERROR_CANCEL=64 on user cancel
# anything else is a real error
# reason: show_dialog() needs a way to exit "Cancel"
#
# writes menus and noise to STDERR

# location of other scripts to source
readonly SHAREDIR="$(dirname ${0})" || exit $?

# source partitioning commons
source "${SHAREDIR}"/partition_common.sh || exit $?

#########################################
## START: dialog functions/definitions ##

# partition_crypt_setup()
# Generate a key, optionnaly encrypt it with gpg
# format a luks or swap partition. Cipher and hash chosen arbitrarily
# uses cryptsetup
# writes the key to STDOUT, in base64 form for gpg
#
# parameters (required)
#  _PARTITION: The partition to encrypt
#  _CRYPTNAME: name used by cryptsetup
#  _CRYPTTYPE: one of: 'swap', 'luks', 'luks-gpg'
#
# return !=0 on failure (including user abort with ERROR_CANCEL=64)
#
partition_crypt_setup() {
	# check input
	check_num_args "${FUNCNAME}" 3 $# || return $?
	local _PARTITION="${1}"
	local _CRYPTNAME="${2}"
	local _CRYPTTYPE="${3}"
	local _UUID=
	# use uuid as temp file name
	_UUID="$(uuidgen)" || return $?
	case "${_CRYPTTYPE}" in
		'swap')
			# TODO swap key is shorter then before with -d /dev/urandom and
			# Maximum keyfile size: 8192kB
			head -c60 /dev/urandom | base64 | head -n1 | tr -d '\n' > /tmp/"${_UUID}" || return $?
			cryptsetup -c aes-xts-plain64:sha512 -s 512 -d /tmp/"${_UUID}" open --type plain "${_PARTITION}" "${_CRYPTNAME}" || return $?
			# print to STDOUT
			cat /tmp/"${_UUID}" || return $?
			# clean up
			rm /tmp/"${_UUID}" || return $?
			;;
		'luks-gpg')
			show_dialog --msgbox "We will now generate a GPG-encrypted luks key for ${_PARTITION}" 0 0 || return $?
			head -c60 /dev/urandom | base64 | head -n1 | tr -d '\n' > /tmp/"${_UUID}" || return $?
			gpg --symmetric --cipher-algo aes256 --armor /tmp/"${_UUID}" || return $?
			gpg --decrypt /tmp/"${_UUID}".asc | cryptsetup -h sha512 -c aes-xts-plain64 -s 512 luksFormat --align-payload=8192 "${_PARTITION}" || return $?
			# TODO, is this needed? Seems not
			# show_dialog --msgbox "Please enter the GPG key for ${_PARTITION} (last time :-)" 0 0 || return $?
			gpg --decrypt /tmp/"${_UUID}".asc | cryptsetup open --type luks "${_PARTITION}" "${_CRYPTNAME}" || return $?
			# print to STDOUT
			cat /tmp/"${_UUID}".asc | base64 -w0 || return $?
			# clean up
			rm /tmp/"${_UUID}".asc || return $?
			rm /tmp/"${_UUID}" || return $?
			;;
		'luks')
			head -c60 /dev/urandom | base64 | head -n1 | tr -d '\n' > /tmp/"${_UUID}" || return $?
			cat /tmp/"${_UUID}" | cryptsetup -h sha512 -c aes-xts-plain64 -s 512 luksFormat --align-payload=8192 "${_PARTITION}" || return $?
			cat /tmp/"${_UUID}" | cryptsetup open --type luks "${_PARTITION}" "${_CRYPTNAME}" || return $?
			# print to STDOUT
			cat /tmp/"${_UUID}" || return $?
			# clean up
			rm /tmp/"${_UUID}" || return $?
			;;
		*)
			echo "ERROR: Unexpected input '${_CRYPTTYPE}' in '${FUNCNAME}'" 1>&2
			return 1
			;;
	esac
	return 0
}

## END: dialog functions/definitions ##
#######################################

#####################
## begin execution ##

# check input
check_num_args "$(basename $0)" 1 $# || exit $?
CONFIG_LIST="${1}"
# sort by mountpoint
CONFIG_LIST="$("${SHAREDIR}"/FSspec sort "${CONFIG_LIST}" 'mountpoint' 0)" || exit $?
CONFIG_ARRAY=(${CONFIG_LIST}) || exit $?
CONFIG_INDEX=0

# validate config
# check if mountpoint '/' exists
if ! "${SHAREDIR}"/FSspec list_haskeyvalue "${CONFIG_LIST}" 'mountpoint' '/'; then
	show_dialog --msgbox "Error: Root partition (with mountpoint '/') not found.\nPlease re-configure the partitions!" 0 0
	exit "${ERROR_CANCEL}"
fi
# validate root partition
CONFIG_ITEM="$("${SHAREDIR}"/FSspec listfind "${CONFIG_LIST}" 'mountpoint' '/')" || exit $?
CRYPTTYPE="$("${SHAREDIR}"/FSspec parse "${CONFIG_ITEM}" 'crypttype')" || exit $?
# root partition encrypted, requires /boot
# check if mountpoint '/boot' exists
if [ "${CRYPTTYPE}" != '' ] && ! "${SHAREDIR}"/FSspec list_haskeyvalue "${CONFIG_LIST}" 'mountpoint' '/boot'; then
	show_dialog --msgbox "Error: Encrypted root partition requires a separate, non-encrypted partition for /boot.\nPlease re-configure the partitions!" 0 0
	exit "${ERROR_CANCEL}"
fi
# check if mountpoint '/boot' is not encrypted
if "${SHAREDIR}"/FSspec list_haskeyvalue "${CONFIG_LIST}" 'mountpoint' '/boot'; then
	CONFIG_ITEM="$("${SHAREDIR}"/FSspec listfind "${CONFIG_LIST}" 'mountpoint' '/boot')" || exit $?
	CRYPTTYPE="$("${SHAREDIR}"/FSspec parse "${CONFIG_ITEM}" 'crypttype')" || exit $?
	if [ "${CRYPTTYPE}" != '' ]; then
		show_dialog --msgbox "Error: /boot cannot be encrypted.\nPlease re-configure the partitions!" 0 0
		exit "${ERROR_CANCEL}"
	fi
fi

# cleanup mounts
"${SHAREDIR}"/FSspec umountall "${CONFIG_LIST}" || exit $?

while [ "${CONFIG_INDEX}" -lt "${#CONFIG_ARRAY[@]}" ]; do
	CONFIG_ITEM="${CONFIG_ARRAY[${CONFIG_INDEX}]}" || exit $?
	PARTITION="$("${SHAREDIR}"/FSspec parse "${CONFIG_ITEM}" 'partition')" || exit $?
	MOUNTPOINT="$("${SHAREDIR}"/FSspec parse "${CONFIG_ITEM}" 'mountpoint')" || exit $?
	FILESYSTEM="$("${SHAREDIR}"/FSspec parse "${CONFIG_ITEM}" 'filesystem')" || exit $?
	CRYPTTYPE="$("${SHAREDIR}"/FSspec parse "${CONFIG_ITEM}" 'crypttype')" || exit $?
	FORMAT="$("${SHAREDIR}"/FSspec parse "${CONFIG_ITEM}" 'format')" || exit $?
	CRYPTNAME=
	CRYPTKEY=
	PARTPATH="${PARTITION}"
	# encrypted
	if [ "${CRYPTTYPE}" != '' ]; then
		# define cryptname
		if [ "${MOUNTPOINT}" != '/' ]; then
			CRYPTNAME="luks_$(basename "${PARTITION}")" || exit $?
		else
			CRYPTNAME='root'
		fi
		CONFIG_ITEM="$("${SHAREDIR}"/FSspec setvalue "${CONFIG_ITEM}" 'cryptname' "${CRYPTNAME}")" || exit $?
		# create crypt-key and use cryptsetup
		CRYPTKEY="$(partition_crypt_setup "${PARTITION}" "${CRYPTNAME}" "${CRYPTTYPE}")" || exit $?
		CONFIG_ITEM="$("${SHAREDIR}"/FSspec setvalue "${CONFIG_ITEM}" 'cryptkey' "${CRYPTKEY}")" || exit $?
		PARTPATH="/dev/mapper/${CRYPTNAME}"
	fi
	sync
	# format partition
	if [ "${FORMAT}" -eq 1 ]; then
		case ${FILESYSTEM} in
			btrfs)		mkfs.btrfs -f ${PARTPATH} >>"${LOG}" 2>&1 || exit $? ;;
			xfs)		mkfs.xfs -f ${PARTPATH} >>"${LOG}" 2>&1 || exit $? ;;
			jfs)		mkfs.jfs -q ${PARTPATH} >>"${LOG}" 2>&1 || exit $? ;;
			reiserfs)	mkreiserfs -f ${PARTPATH} >>"${LOG}" 2>&1 || exit $?r;;
			ext2)		mke2fs "${PARTPATH}" -F >>"${LOG}" 2>&1 || exit $? ;;
			ext3)		mke2fs -j ${PARTPATH} -F >>"${LOG}" 2>&1 || exit $? ;;
			ext4)		mke2fs -t ext4 ${PARTPATH} -F >>"${LOG}" 2>&1 || exit $? ;;
			vfat)		mkfs.vfat ${PARTPATH} >>"${LOG}" 2>&1 || exit $? ;;
			# swap
			swap)		mkswap ${PARTPATH} >>"${LOG}" 2>&1 || exit $? ;;
			*)
				echo "ERROR: Unexpected file sytem '${FILESYSTEM}' for '${PARTITION}'" 1>&2
				exit 1 ;;
		esac
		FORMAT='0'
		CONFIG_ITEM="$("${SHAREDIR}"/FSspec setvalue "${CONFIG_ITEM}" 'format' "${FORMAT}")" || exit $?
		sync
	fi
	# mount actions
	if [ "${FILESYSTEM}" != 'swap' ]; then
		# create our mount directory
		mkdir -p "${DESTDIR}${MOUNTPOINT}" || exit $?
		# mount the bad boy
		mount -t "${FILESYSTEM}" "${PARTPATH}" "${DESTDIR}${MOUNTPOINT}" >>"${LOG}" 2>&1 || exit $?
	else
		swapon ${PARTPATH} >>"${LOG}" 2>&1 || exit $?
	fi
	# write FSspec back to array
	CONFIG_ARRAY[${CONFIG_INDEX}]="${CONFIG_ITEM}" || exit $?
	# increment array index
	CONFIG_INDEX="$((CONFIG_INDEX+1))" || exit $?
done

# rewrite list from array
CONFIG_LIST="${CONFIG_ARRAY[@]}"
# default sort by partition again
CONFIG_LIST="$("${SHAREDIR}"/FSspec sort "${CONFIG_LIST}" 'partition' 0)" || exit $?

# everything ok, write new config to STDOUT
echo -n "${CONFIG_LIST}" || exit $?

exit 0
